/*
 * Created by Jaren at 2021/8/18 10:38
 */
import 'dart:typed_data';

import 'package:crclib/catalog.dart';
import 'package:inmotion_protocol/base/base_message.dart';
import 'package:inmotion_protocol/common/accessory_info.dart';
import 'package:inmotion_protocol/common/product_model.dart';
import 'package:inmotion_protocol/common/product_version.dart';
import 'package:inmotion_protocol/lorin/bean/base/tbox.dart';
import 'package:inmotion_protocol/lorin/bean/common/date.dart';
import 'package:inmotion_protocol/lorin/constant.dart';
import 'package:inmotion_protocol/lorin/lorin_message.dart';
import 'package:inmotion_protocol/util/byte_util.dart';
import 'package:inmotion_protocol/util/int8_list_buffer.dart';
import 'package:inmotion_protocol/util/log.dart';

class Util {
  static Uint8ClampedList genCmd(int cmd, bool read) {
    int length = 0;
    if (cmd < 0x20) {
      length = 1;
    } else if (cmd < 0x1000) {
      length = 2;
    } else if (cmd < 0x80000) {
      length = 3;
    } else if (cmd < 0x4000000) {
      length = 4;
    } else {
      LogUtils.e(
          "Invalid cmd 0x" +
              ByteUtil.bytes2HexString(ByteUtil.num2Bytes(cmd, 4)),
          Exception("Invalid cmd, cmd must be less than 0x4000000."));
      return Uint8ClampedList(length);
    }
    Uint8ClampedList result = ByteUtil.num2Bytes(cmd, length);
    // 标识读写
    result[0] = (result[0] | (read ? 0 : Constant.cmdTypeWrite)) & 0xFF;

    if (result.length > 1) {
      // 标识下个byte仍是cmd
      result[0] = (result[0] | Constant.firstHasNextCmd) & 0xFF;
      for (int i = 1; i < length; i++) {
        result[i] = (result[i] | Constant.hasNextCmd) & 0xFF;
      }
      // 还原最后1byte的bit7至0
      result[length - 1] =
          (result[length - 1] ^ (0xFF & Constant.hasNextCmd)) & 0xFF;
    }
    return result;
  }

  static int calCmdLength(Uint8ClampedList bytes, int offset) {
    if (bytes.length <= offset) {
      return 0;
    }
    int cmdLength = 1;
    int cmd = bytes[offset];
    if (!hasNextCmd(cmd, true)) {
      return cmdLength;
    }

    for (int i = offset + 1, length = offset + Constant.maxCmdLength - 1;
        i < length;
        i++) {
      cmdLength++;
      if (hasNextCmd(bytes[i], false)) {
        continue;
      }
      break;
    }
    return cmdLength;
  }

  static bool hasNextCmd(int cmd, bool first) {
    if (first) {
      return (cmd & Constant.firstHasNextCmd) == Constant.firstHasNextCmd;
    }
    return (cmd & Constant.hasNextCmd) == Constant.hasNextCmd;
  }

  static Uint8ClampedList genTab(int tab) {
    int length = 0;
    if (tab < 0x7F) {
      length = 1;
    } else if (tab < 0x3FFF) {
      length = 2;
    } else if (tab < 0x1FFFFF) {
      length = 3;
    } else if (tab < 0xFFFFFFF) {
      length = 4;
    }
    Uint8ClampedList bytes = Uint8ClampedList(length);

    for (int i = 0; i < length; i++) {
      int v = (tab >> (7 * (length - i - 1)) | 0x80);
      if (v > 0xFF) {
        v = v & 0xFF;
      }
      bytes[i] = v;
    }
    bytes[length - 1] = (bytes[length - 1] ^ 0x80);
    return bytes;
  }

  static int calTab(Uint8ClampedList bytes, int offset, int length) {
    int tab = 0;
    int index = 0;
    while (index < length) {
      int b = bytes[offset + index];
      if ((b & 0x80) == 0x80) {
        b ^= 0x80;
      }
      tab = tab | (b << (7 * (length - index - 1)));
      index++;
    }
    return tab;
  }

  static int calTabLength(dynamic bytes, int offset) {
    if (bytes is Uint8ClampedList) {
      int tabLength = 1; // 当前tab的最高位表示后面还有没有tab，所以应该从1开始
      while (hasNextTab(bytes[offset + tabLength - 1]) &&
          offset + tabLength - 1 < bytes.length &&
          tabLength < Constant.maxTabLength) {
        tabLength++;
      }
      return tabLength;
    } else if (bytes is Int8ListBuffer) {
      if (!bytes.isReadable()) return 0;
      int oldReaderIndex = bytes.readerIndex;
      bytes.readerIndex = offset;
      int tab = bytes.readByte();
      int tabLength = 1;
      while (hasNextTab(tab) &&
          bytes.isReadable() &&
          tabLength < Constant.maxTabLength) {
        tabLength++;
        tab = bytes.readByte();
      }

      // 恢复readerIndex
      bytes.readerIndex = oldReaderIndex;
      return tabLength;
    } else {
      return 0;
    }
  }

  static bool hasNextTab(int tab) {
    return (tab & Constant.hasNextTab) == Constant.hasNextTab;
  }

  static int calCheckLength(int format) {
    if (Util.isCRC32(format)) return 4;
    if (Util.isCRC16(format)) return 2;
    if (Util.isEvenParityCheck(format)) return 1;
    return 0;
  }

  static bool isEvenParityCheck(int format) {
    return (format & Constant.checkTypeParity) == Constant.checkTypeParity &&
        !isCRC32(format);
  }

  static bool isCRC16(int format) {
    return (format & Constant.checkTypeCRC16) == Constant.checkTypeCRC16 &&
        !isCRC32(format);
  }

  static bool isCRC32(int format) {
    return (format & Constant.checkTypeCRC32) == Constant.checkTypeCRC32;
  }

  static bool isGeneralOne2OneData(int format) {
    return (format & Constant.dataTypeGeneralOne2One) ==
        Constant.dataTypeGeneralOne2One;
  }

  static bool isGeneralOne2ManyData(int format) {
    return (format & Constant.dataTypeGeneralOne2Many) ==
        Constant.dataTypeGeneralOne2Many;
  }

  static bool isGeneralMany2ManyData(int format) {
    return (format & Constant.dataTypeGeneralMany2Many) ==
        Constant.dataTypeGeneralMany2Many;
  }

  static bool isSpecialOne2OneData(int format) {
    return (format & Constant.dataTypeSpecialOne2One) ==
        Constant.dataTypeSpecialOne2One;
  }

  static bool isSpecialOne2ManyData(int format) {
    return (format & Constant.dataTypeSpecialOne2Many) ==
        Constant.dataTypeSpecialOne2Many;
  }

  static bool isSpecialMany2ManyData(int format) {
    return (format & Constant.dataTypeSpecialMany2Many) ==
        Constant.dataTypeSpecialMany2Many;
  }

  static bool isSpecialData(int format) {
    return isSpecialMany2ManyData(format) ||
        isSpecialOne2ManyData(format) ||
        isSpecialOne2OneData(format);
  }

  static bool isGeneralData(int format) {
    return isGeneralMany2ManyData(format) ||
        isGeneralOne2ManyData(format) ||
        isGeneralOne2OneData(format);
  }

  static int calFormat(
      int tabType, int checkType, int encryptType, int dataType) {
    int format = 0;
    format |= tabType;
    format |= checkType;
    format |= encryptType;
    format |= dataType;
    return ByteUtil.toInt8(format).value;
  }

  static Uint8ClampedList calculateEvenParity(Uint8ClampedList data,
      [int offset = 0, int length = 0]) {
    if (length == 0) length = data.length;
    int result = 0;
    for (int i = offset, max = offset + length; i < max; i++) {
      result ^= data[i];
    }
    Uint8ClampedList check = Uint8ClampedList(1);
    check[0] = result;
    return check;
  }

  /// CRC32校验算法
  /// 高位在左，低位在右（即小端模式）
  static Uint8ClampedList crc32(Uint8ClampedList data,
      [int offset = 0, int length = 0]) {
    return ByteUtil.num2BytesLe(Crc32Xz().convert(data).toBigInt().toInt(), 4);
  }

  /// CRC32校验算法
  /// 高位在左，低位在右（即小端模式）
  static Uint8ClampedList crc32_2(Uint8List data,
      [int offset = 0, int length = 0]) {
    return ByteUtil.num2BytesLe(Crc32Xz().convert(data).toBigInt().toInt(), 4);
  }

  /// 基于Modbus CRC16的校验算法
  /// 低位在左，高位在右
  static Uint8ClampedList crc16(Uint8ClampedList bytes,
      [int offset = 0, int length = 0]) {
    if (length == 0) length = bytes.length;
    if (bytes.isEmpty || bytes.length < offset + length) {
      return Uint8ClampedList(0);
    }
    // 预置 1 个 16 位的寄存器为十六进制FFFF, 称此寄存器为 CRC寄存器。
    int crc = 0xFFFF;
    for (int i = offset, max = offset + length; i < max; i++) {
      // 把第一个 8 位二进制数据 与 16 位的 CRC寄存器的低 8 位相异或, 把结果放于 CRC寄存器
      crc = ((crc & 0xFF00) | (crc & 0x00FF) ^ (bytes[i] & 0xFF));
      for (int j = 0; j < 8; j++) {
        // 把 CRC 寄存器的内容右移一位( 朝低位)用 0 填补最高位, 并检查右移后的移出位
        if ((crc & 0x0001) > 0) {
          // 如果移出位为 1, CRC寄存器与多项式A001进行异或
          crc = crc >> 1;
          crc = crc ^ 0xA001;
        } else {
          // 如果移出位为 0,再次右移一位
          crc = crc >> 1;
        }
      }
    }
    return ByteUtil.num2BytesLe(crc, 2);
  }

  /// 解析云盒IMEI信息
  static String parseTBoxInfoByIMEI(Uint8ClampedList? bytes) {
    int minLength = 2 + 4 + 4 + 2 + 2 + 16 + 16 + 21 + 2;
    if (bytes == null || bytes.length < minLength) {
      throw Exception('Invalid IMEI param!');
    }
    int offset = 14;
    int imeiLength = 16;
    bytes = bytes.sublist(offset, offset + imeiLength);
    return ByteUtil.bytes2String(bytes);
  }

  /// 解析云盒车辆deviceID (专用消息结构)
  static String parseTBoxByDeviceId(Uint8ClampedList? bytes) {
    int minLength = 2 + 17 + 17;
    if (bytes == null || bytes.length < minLength) {
      throw Exception('Invalid DeviceId param!');
    }
    int offset = 19;
    int deviceIdLength = 17;
    bytes = bytes.sublist(offset, offset + deviceIdLength);
    return ByteUtil.bytes2String(bytes);
  }

  /// 解析云盒车辆deviceID（通用消息结构）
  static String parseTBoxDeviceIdByGeneralMessage(Uint8ClampedList? bytes) {
    int minLength = 16;
    if (bytes == null || bytes.length < minLength) {
      throw Exception('Invalid DeviceId param!');
    }
    bytes = bytes.sublist(0, 15);
    return ByteUtil.bytes2String(bytes);
  }

  /// 解析云盒用户名与密码
  static List<String> parseTBoxUserInfo(Uint8ClampedList? bytes) {
    int minLength = 2 + 2 + 33 + 33;
    if (bytes == null || bytes.length < minLength) {
      throw Exception('Invalid UserInfo param!');
    }
    int offset = 4;
    var byte1 = bytes.sublist(offset, offset + 33);
    var byte2 = bytes.sublist(offset + 33, offset + 66);
    return [ByteUtil.bytes2String(byte1), ByteUtil.bytes2String(byte2)];
  }

  /// 解析云盒ESIM卡列表
  static List<EsimModel> parseTBoxEsimList(Uint8ClampedList? bytes) {
    int offset = 3;
    if (bytes == null || bytes.length < 95) {
      throw Exception('Invalid TBoxEsimList param!');
    }
    List<EsimModel> list = [];
    var byte1 = bytes.sublist(offset, offset + 23);
    if (!byte1.every((element) => element == 0)) {
      list.add(EsimModel.fromBytes(byte1));
    }
    offset += 23;
    var byte2 = bytes.sublist(offset, offset + 23);
    if (!byte2.every((element) => element == 0)) {
      list.add(EsimModel.fromBytes(byte2));
    }
    offset += 23;
    var byte3 = bytes.sublist(offset, offset + 23);
    if (!byte3.every((element) => element == 0)) {
      list.add(EsimModel.fromBytes(byte3));
    }
    offset += 23;
    var byte4 = bytes.sublist(offset, offset + 23);
    if (!byte4.every((element) => element == 0)) {
      list.add(EsimModel.fromBytes(byte4));
    }
    return list;
  }

  static TBoxInfo parseTBoxRealTimeInfo(Uint8ClampedList? bytes) {
    TBoxInfo info = TBoxInfo();
    if (bytes == null) {
      throw Exception('Invalid TBoxRealTimeInfo param!');
    }
    int offset = 6;
    // TBOX_GENERAL_DATA
    offset += 2;
    int val = ByteUtil.bytes2IntLe(bytes.sublist(offset, offset + 1));
    info.networkState = val == 0
        ? TBoxNetworkState.disconnect
        : val == 1
            ? TBoxNetworkState.connected
            : TBoxNetworkState.connecting;
    offset += 2;
    info.rssi = ByteUtil.bytes2IntLe(bytes.sublist(offset, offset + 1));
    return info;
  }

  /// 解析SN
  /// 若SN异常（首个字节为0）则返回0000000000000000
  static String parseSn(Uint8ClampedList? bytes, [int offset = 0]) {
    if (bytes == null) {
      return "0000000000000000";
    }
    if (bytes[offset] == 0) return '0000000000000000';
    int validLength = 0;
    for (int i = offset; i < bytes.length; i++) {
      if (bytes[i] == 0) {
        validLength = i;
        break;
      }
    }
    if (validLength > 0) {
      bytes = bytes.sublist(offset, validLength);
    }
    return ByteUtil.bytes2String(bytes);
  }

  static ProductModel parseModel(Uint8ClampedList? bytes) {
    if (bytes == null || bytes.length < ProductModel.minLength) {
      throw Exception('Invalid model param!');
    }
    return ProductModel(
        ByteUtil.bytes2Uint8Le(bytes),
        ByteUtil.bytes2Uint8Le(bytes, 1),
        ByteUtil.bytes2Uint8Le(bytes, 2),
        ByteUtil.bytes2Uint8Le(bytes, 3),
        ByteUtil.bytes2Uint8Le(bytes, 4),
        ByteUtil.bytes2Uint8Le(bytes, 5));
  }

  static Datetime parseDate(LorinGeneralMessageData msgData) {
    if (msgData.para == null || msgData.para!.length < Datetime.length) {
      throw Exception('Invalid date param!');
    }
    var date = Datetime();
    Datetime.fromBytes(msgData.para!, output: date);
    return date;
  }

  /// 解析各设备软件版本号
  static VersionWrapper parseFwVersion(Uint8ClampedList? bytes,
      [int? deviceNum, bool isBms = false]) {
    int minLength = 1 + 4 + 4;
    if (bytes == null || bytes.length < minLength) {
      throw Exception('Invalid version param!');
    }
    VersionWrapper versionWrapper = VersionWrapper();
    int offset = 0;
    print(
        "parseFwVersion len = ${bytes.length}, deviceNum = $deviceNum, isBms = $isBms");
    if (deviceNum != null && isBms) {
      offset += 8;
      String appVersion = formatVersion(bytes, offset, 4);
      offset += 4;
      String bootloaderVersion = formatVersion(bytes, offset, 4);
      Version version = Version(
          ByteUtil.toUint8(deviceNum), appVersion, bootloaderVersion, '');
      versionWrapper.put(deviceNum, version);
    } else {
      int count = (bytes.length ~/ minLength);
      for (int i = 0; i < count; i++) {
        int device = bytes[offset];
        offset++;
        String appVersion = formatVersion(bytes, offset, 4);
        offset += 4;
        String bootloaderVersion = formatVersion(bytes, offset, 4);
        offset += 4;
        Version version = Version(
            ByteUtil.toUint8(device), appVersion, bootloaderVersion, '');
        versionWrapper.put(device, version);
      }
    }

    return versionWrapper;
  }

  /// 解析单个设备软件版本号
  static Version parseSingleFmVersion(Uint8ClampedList? bytes) {
    int minLength = 1 + 4 + 4;
    if (bytes == null || bytes.length < minLength) {
      throw Exception('Invalid version param!');
    }
    int offset = 0;
    int device = bytes[offset];
    offset++;
    String appVersion = formatVersion(bytes, offset, 4);
    offset += 4;
    String bootloaderVersion = formatVersion(bytes, offset, 4);
    offset += 4;
    return Version(ByteUtil.toUint8(device), appVersion, bootloaderVersion, '');
  }

  /// 格式化某设备版本号
  static String formatVersion(Uint8ClampedList bytes, int offset, int length) {
    if (bytes.length < offset + length) {
      return "";
    }
    return (bytes[offset + 2 + 1] & 0xFF).toString() +
        "." +
        (bytes[offset + 2] & 0xFF).toString() +
        "." +
        ByteUtil.bytes2UIntLe(bytes, offset, 2).toString();
  }

  /// 格式化硬件版本号
  static VersionWrapper parseHwVersion(Uint8ClampedList? bytes,
      {VersionWrapper? output}) {
    output ??= VersionWrapper();
    if (bytes == null) return output;

    int minLength = 1 + 2;
    if (bytes.length < minLength) {
      throw Exception('Invalid version param!');
    }

    int count = (bytes.length ~/ minLength);
    int offset = 0;
    for (int i = 0; i < count; i++) {
      int device = bytes[offset];
      offset++;
      String hwVersion = formatHwVersion(bytes, offset, 2);
      offset += 2;

      Version? version = output.getByDevice(device);
      version ??= Version(ByteUtil.toUint8(device), "", "", hwVersion);
      version.hwVersion = hwVersion;
      output.put(device, version);
    }
    return output;
  }

  /// 格式化硬件版本号
  static String formatHwVersion(
      Uint8ClampedList bytes, int offset, int length) {
    if (bytes.isEmpty || bytes.length < offset + length) {
      return "--";
    }

    return (bytes[offset + 1] & 0xFF).toString() +
        "." +
        (bytes[offset] & 0xFF).toString();
  }

  static String parseDeviceId(Uint8ClampedList bytes) {
    return ByteUtil.bytes2HexString(bytes);
  }

  static AccessoryWrapper parseAccessories(String id, Uint8ClampedList bytes,
      {AccessoryWrapper? output}) {
    List<String> tmpSnList = ByteUtil.bytes2StringList(bytes);
    List<String> snList = [];
    for (var i = 0; i < tmpSnList.length; i++) {
      var tmpSpiltSnList = tmpSnList[i].split(",");
      if (tmpSpiltSnList.isEmpty) {
        snList.add(tmpSnList[i]);
      } else {
        snList.addAll(tmpSpiltSnList);
      }
    }
    output ??= AccessoryWrapper();
    Accessory? accessory = output.get(id);
    accessory ??= Accessory(id, snList);
    output.put(id, accessory);

    return output;
  }

  static int? whichDeviceRequestingUpdate(IMessage msg) {
    if (msg is! LorinMessage) return null;
    if (msg.data is! LorinGeneralMessageData) return null;
    var msgData = msg.data as LorinGeneralMessageData;
    if (msgData.channel.value != Constant.channelCtrl) return null;

    if ((msgData.channel.value & 0x0F) != Constant.channelCtrl) return null;
    if ((msgData.property.value & 0x0F) != Constant.PROPERTY_FIRMWARE_STATUS)
      return null;

    /// 默认通讯设备号
    if (msgData.source == null) return 0;
    return msgData.source!.value;
  }
}
